package edu.scau.divineplace.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @Author: x1aolone
 * @Date: 2020/8/21 17:11
 */
@Slf4j
@Component
public class RedisUtil {

    @Autowired
    private static RedisTemplate redisTemplate;

    // Key相关的操作

    /**
     *判断redis中是否已存在key
     * @param key
     * @return true/false
     *
     */
    public static boolean isExistKey(String key){
        if (StringUtils.isBlank(key)){
            return false;
        }
        return redisTemplate.hasKey(key);
    }


    /**
     * Clears this cache instance
     */
    public static void clear() {
        redisTemplate.execute((RedisCallback) connection -> {
            connection.flushDb();
            return null;
        });
    }


    /**
     * Remove cached query result from redis
     * @param key
     * @return
     */
    public static Object removeObject(Object key) {
        try {
            redisTemplate.delete(key);
            log.debug("Remove cached query result from redis");
        }
        catch (Throwable t) {
            log.error("Redis remove failed", t);
        }
        return null;
    }


    // String类型的相关操作

    /**
     *  set to redis
     * @param key
     * @param value
     */
    public static void set(String key, Object value) {
        try {
            log.debug("Put query result to redis");
            redisTemplate.opsForValue().set(key,value);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    /***
     * set to redis only if key is absent
     */
    public static void setIfAbsent(String key, Object value) {
        try {
            log.debug("Put query result to redis");
            redisTemplate.opsForValue().setIfAbsent(key,value);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    /**
     * Put query result to redis
     *
     * @param key
     * @param value
     */
    public static void setWithExpire(String key, Object value, long EXPIRE_TIME, TimeUnit timeUnit) {
        try {
            log.debug("Put query result to redis");
            redisTemplate.opsForValue().set(key, value, EXPIRE_TIME, timeUnit);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    /***
     * get from redis
     * @param key
     */
    public static Object get(String key) {
        try {
            log.debug("get string from redis");
            return redisTemplate.opsForValue().get(key);
        }
        catch (Throwable t) {
            log.error("get string failed", t);
        }
        return null;
    }

    /**
     *判断redis中是否已存在key对应的String value
     * @param key
     * @return true/false
     *
     */
    public static boolean isExistValue(String key){
        if (StringUtils.isBlank(key)){
            return false;
        }
        return get(key) != null;
    }


    // list类型相关的操作

    /**
     *  left push to redis
     * @param key
     * @param value
     */
    public static void leftPushToList(String key, Object... value) {
        try {
            log.debug("left push to redis");
            redisTemplate.opsForList().leftPush(key, value);
        }
        catch (Throwable t) {
            log.error("Redis left push failed", t);
        }
    }

    /**
     *  left push all to redis
     * @param key
     * @param value
     */
    public static void leftPushAllToList(String key, List value) {
        try {
            log.debug("left push all to redis");
            redisTemplate.opsForList().leftPushAll(key, value);
        }
        catch (Throwable t) {
            log.error("Redis left push failed", t);
        }
    }

    /**
     *  right push to redis
     * @param key
     * @param value
     */
    public static void rightPushToList(String key, Object... value) {
        try {
            log.debug("right push to redis");
            redisTemplate.opsForList().rightPush(key, value);
        }
        catch (Throwable t) {
            log.error("Redis right push failed", t);
        }
    }

    /**
     *  right push all to redis
     * @param key
     * @param value
     */
    public static void rightPushAllToList(String key, List value) {
        try {
            log.debug("right push all to redis");
            redisTemplate.opsForList().rightPushAll(key, value);
        }
        catch (Throwable t) {
            log.error("Redis right push failed", t);
        }
    }


    /**
     * left pop to redis
     * @param key
     * @return
     */
    public static Object leftPopFromList(String key) {
        try {
            log.debug("left pop to redis");
            return redisTemplate.opsForList().leftPop(key);
        }
        catch (Throwable t) {
            log.error("Redis left pop failed", t);
        }
        return null;
    }

    /**
     * right pop to redis
     * @param key
     */
    public static Object rightPopFromList(String key) {
        try {
            log.debug("right pop to redis");
            redisTemplate.opsForList().rightPop(key);
        }
        catch (Throwable t) {
            log.error("Redis right pop failed", t);
        }
        return null;
    }


    /**
     * @param key
     * @param index
     * @return
     */
    public static Object setListValue(String key, long index, Object value) {
        try {
            log.debug("set list value to redis");
            redisTemplate.opsForList().set(key, index, value);
        }
        catch (Throwable t) {
            log.error("Redis set list value failed", t);
        }
        return null;
    }


    /**
     * @param key
     * @param index
     * @return
     */
    public static Object getFromList(String key, long index) {
        try {
            log.debug("Get list value from redis");
            redisTemplate.opsForList().index(key, index);
        }
        catch (Throwable t) {
            log.error("Redis get list value failed", t);
        }
        return null;
    }


    /**
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public static List getFromList(String key, long start, long end) {
        try {
            log.debug("Get list value from redis");
            redisTemplate.opsForList().range(key, start, end);
        }
        catch (Throwable t) {
            log.error("Redis get list value failed", t);
        }
        return Collections.emptyList();
    }


    /**
     *
     * @param key
     * @return
     */
    public static List getList(String key) {
        return getFromList(key, 0, -1);
    }



    /**
     *
     * @param key
     * @return
     */
    public static Long getListSize(String key) {
        try {
            log.debug("Get list size from redis");
            redisTemplate.opsForList().size(key);
        }
        catch (Throwable t) {
            log.error("Redis get size value failed", t);
        }
        return -1L;
    }


    /**
     * 删除key对应的list中，值为value的第一个元素
     * @param key
     * @param value
     */
    public static void removeFromList(String key, Object value) {
        try {
            log.debug("Remove list value from redis");
            redisTemplate.opsForList().remove(key, 1L, value);
        }
        catch (Throwable t) {
            log.error("Redis remove list value failed", t);
        }
    }


    /**
     *判断redis中key对应的List是否存在该成员
     * @param key
     * @return true/false
     */
    public static boolean isExistListValue(String key, Object value){
        if (StringUtils.isBlank(key)){
            return false;
        }
        List<Object> list = getList(key);
        for (Object o : list) {
            if (o.equals(value)) {
                return true;
            }
        }
        return false;
    }



    // hash类型相关的操作

    /**
     * set hash to redis
     * @param key
     * @param hashKey
     * @param hashValue
     */
    public static void setToHash (String key, Object hashKey, Object hashValue) {
        try {
            log.debug("Put query result to redis");
            redisTemplate.opsForHash().put(key, hashKey, hashValue);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    /**
     * set hash to redis if absent
     * @param key
     * @param hashKey
     * @param hashValue
     */
    public static void setToHashIfAbsent (String key, Object hashKey, Object hashValue) {
        try {
            log.debug("Put query result to redis");
            redisTemplate.opsForHash().putIfAbsent(key, hashKey, hashValue);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    /**
     * get hash from redis
     * @param key
     * @param hashKey
     */
    public static Object getFromHash(String key, Object hashKey) {
        try {
            log.debug("get hash from redis");
            return redisTemplate.opsForHash().get(key, hashKey);
        }
        catch (Throwable t) {
            log.error("get hash failed", t);
        }
        return null;
    }

    /**
     * del hashValue from redis
     * @param key
     * @param hashKeys
     */
    public static Object delFromHash (String key, Object... hashKeys) {
        try {
            log.debug("get hash from redis");
            return redisTemplate.opsForHash().get(key, hashKeys);
        }
        catch (Throwable t) {
            log.error("get hash failed", t);
        }
        return null;
    }


    /**
     * 判断redis中是否已存在key、hashKey对应的hash value
     * @param key
     * @return true/false
     *
     */
    public static boolean isExistHashValue(String key, Object hashKey){
        if (StringUtils.isBlank(key)){
            return false;
        }
        return getFromHash(key, hashKey) != null;
    }


    // set类型相关的操作
    /**
     *  set to redis
     * @param key
     * @param value
     */
    public static void addToSet(String key, Object... value) {
        try {
            log.debug("Put query result to redis");
            redisTemplate.opsForSet().add(key, value);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }


    /***
     * remove from redis
     */
    public static void removeFromSet(String key, Object... value) {
        try {
            log.debug("remove from redis");
            redisTemplate.opsForSet().remove(key,value);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }


    /***
     * get from redis
     * @param key
     */
    public static Set getSet(String key) {
        try {
            log.debug("get set from redis");
            return redisTemplate.opsForSet().members(key);
        }
        catch (Throwable t) {
            log.error("get hash failed", t);
        }
        return Collections.emptySet();
    }

    /**
     *判断redis中key对应的Set是否存在该成员
     * @param key
     * @return true/false
     *
     */
    public static boolean isExistSetValue(String key, Object value){
        if (StringUtils.isBlank(key)){
            return false;
        }
        return redisTemplate.opsForSet().isMember(key, value);
    }


    // zset类型相关的操作


    public static void addToZset(String key, Object member, double score) {
        try {
            log.debug("set Zset to redis");
            redisTemplate.opsForZSet().add(key, member, score);
        }
        catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    /**
     * 返回redis指定key的zset中member对应的score(数值)
     * @param key
     * @param member
     * @return
     */
    public static Double getScoreFromZset(String key, Object member) {
        try {
            log.debug("get zset score from redis");
             return redisTemplate.opsForZSet().score(key,member);
        }
        catch (Throwable t) {
            log.error("Redis get zset score failed", t);
        }
        return null;
    }


    /***
     * get from redis
     * @param key
     */
    public static Set getZset(String key) {
        try {
            log.debug("get set from redis");
            return redisTemplate.opsForZSet().range(key, 0L, -1L);
        }
        catch (Throwable t) {
            log.error("get hash failed", t);
        }
        return null;
    }

    /**
     *判断redis中key对应的Set是否存在该成员
     * @param key
     * @return true/false
     *
     */
    public static boolean isExistZsetValue(String key, Object value) {
        if (StringUtils.isBlank(key)){
            return false;
        }
        Set zset = getZset(key);
        for(Object o : zset) {
            if (o.equals(value)) {
                return true;
            }
        }
        return false;
    }


    // GEO类型的相关操作

    /**
     * Put geo result to redis
     *
     * @param key
     * @param m
     */
    @SuppressWarnings("unchecked")
    public static Long addGeo(Object key, Point point, Object m) {
        try {
            log.debug("addGeo result to redis");
            return redisTemplate.opsForGeo().add(key,point,m);

        }
        catch (Throwable t) {
            log.error("geoAdd failed", t);
        }
        return null;
    }

    /**
     * geo dist result to redis
     *
     * @param key
     * @param m1,m2
     */
    @SuppressWarnings("unchecked")
    public static Distance distanceGeo(Object key, Object m1, Object m2) {
        try {
            log.debug("distanceGeo to redis");
            return  redisTemplate.opsForGeo().distance(key,m1,m1);
        }
        catch (Throwable t) {
            log.error("distanceGeo failed", t);
        }
        return null;
    }

    /**
     * nearDist
     * @param key
     * @param circle
     * @param args
     * @return
     */
    public static GeoResults<RedisGeoCommands.GeoLocation> nearDistance(Object key, Circle circle, RedisGeoCommands.GeoRadiusCommandArgs args) {
        try {
            log.debug("nearDistance to redis");
            return  redisTemplate.opsForGeo().radius(key,circle,args);
        }
        catch (Throwable t) {
            log.error("nearDistance failed", t);
        }
        return null;
    }




}